package com.jzo2o.market.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.CommonUserApi;
import com.jzo2o.api.customer.dto.response.CommonUserResDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.common.constants.ErrorInfo;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUseBackService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.mysql.utils.PageUtils;
import com.jzo2o.redis.utils.RedisSyncQueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static com.jzo2o.common.constants.ErrorInfo.Code.SEIZE_COUPON_FAILD;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;
import static com.jzo2o.market.enums.CouponStatusEnum.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

	@Resource(name = "seizeCouponScript")
	private DefaultRedisScript<String> seizeCouponScript;
	@Resource
	private RedisTemplate<String, Object> redisTemplate;

	@Resource
	private IActivityService activityService;
	@Resource
	private CommonUserApi commonUserApi;
	@Resource
	private ICouponUseBackService couponUseBackService;
	@Resource
	private ICouponWriteOffService couponWriteOffService;

	@Override
	public PageResult<CouponInfoResDTO> queryForPageOfOperation(CouponOperationPageQueryReqDTO couponOperationPageQueryReqDTO) {
		// 1.数据校验
		if (ObjectUtils.isNull(couponOperationPageQueryReqDTO.getActivityId())) {
			throw new BadRequestException("请指定活动");
		}
		// 2.数据查询
		// 分页 排序
		Page<Coupon> couponQueryPage = PageUtils.parsePageQuery(couponOperationPageQueryReqDTO, Coupon.class);
		// 查询条件
		LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
		lambdaQueryWrapper.eq(Coupon::getActivityId, couponOperationPageQueryReqDTO.getActivityId());
		// 查询数据
		Page<Coupon> couponPage = baseMapper.selectPage(couponQueryPage, lambdaQueryWrapper);

		// 3.数据转化，并返回
		return PageUtils.toPage(couponPage, CouponInfoResDTO.class);
	}

	@Override
	public List<CouponInfoResDTO> queryForList(Long lastId, Long userId, Integer status) {

		// 1.校验
		if (status > 3 || status < 1) {
			throw new BadRequestException("请求状态不存在");
		}
		// 2.查询准备
		LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
		// 查询条件
		lambdaQueryWrapper.eq(Coupon::getStatus, status).eq(Coupon::getUserId, userId).lt(ObjectUtils.isNotNull(lastId),
			Coupon::getId,
			lastId
		);
		// 查询字段
		lambdaQueryWrapper.select(Coupon::getId);
		// 排序
		lambdaQueryWrapper.orderByDesc(Coupon::getId);
		// 查询条数限制
		lambdaQueryWrapper.last(" limit 10 ");
		// 3.查询数据(数据中只含id)
		List<Coupon> couponsOnlyId = baseMapper.selectList(lambdaQueryWrapper);
		// 判空
		if (CollUtils.isEmpty(couponsOnlyId)) {
			return new ArrayList<>();
		}

		// 4.获取数据且数据转换
		// 优惠id列表
		List<Long> ids = couponsOnlyId.stream().map(Coupon::getId).collect(Collectors.toList());
		// 获取优惠券数据
		List<Coupon> coupons = baseMapper.selectBatchIds(ids);
		// 数据转换
		return BeanUtils.copyToList(coupons, CouponInfoResDTO.class);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void revoke(Long activityId) {
		lambdaUpdate().set(Coupon::getStatus, VOIDED.getStatus())
			.eq(Coupon::getActivityId, activityId)
			.eq(Coupon::getStatus, NO_USE.getStatus())
			.update();
	}

	@Override
	public Integer countReceiveNumByActivityId(Long activityId) {
		return lambdaQuery().eq(Coupon::getActivityId, activityId).count();
	}

	@Override
	public void processExpireCoupon() {
		lambdaUpdate().set(Coupon::getStatus, INVALID.getStatus())
			.eq(Coupon::getStatus, NO_USE.getStatus())
			.le(Coupon::getValidityTime, DateUtils.now())
			.update();
	}

	/**
	 * 领取优惠券
	 *
	 * @param activityId 活动id
	 * @return:
	 */
	@Override
	public void seizeCoupon(Long activityId) {
		Long userId = UserContext.currentUserId();
		if (ObjectUtils.isNull(userId)) {
			throw new CommonException("请先登录");
		}
		// 1. 根据活动 ID 从 Redis 查询活动
		ActivityInfoResDTO activityInfo = activityService.getActivityInfoByIdFromCache(activityId);
		// 2. 判断活动当前状态
		LocalDateTime now = DateUtils.now();
		if (ObjectUtils.isNull(activityInfo) || activityInfo.getDistributeStartTime().isAfter(now)) {
			throw new CommonException(SEIZE_COUPON_FAILD, "活动还未开始");
		}
		if (ObjectUtils.isNull(activityInfo) || activityInfo.getDistributeEndTime().isBefore(now)) {
			throw new CommonException(SEIZE_COUPON_FAILD, "活动已结束");
		}
		// 3. 领取优惠卷
		int index = (int) (activityId % 10);
		// 同步队列key
		String couponSyncQueueKey = RedisSyncQueueUtils.getQueueRedisKey(COUPON_SEIZE_SYNC_QUEUE_NAME, index);
		// 库存key
		String stockKey = String.format(COUPON_RESOURCE_STOCK, index);
		// 抢卷成功key
		String successKey = String.format(COUPON_SEIZE_LIST, activityId, index);
		List<String> keys = List.of(couponSyncQueueKey, stockKey, successKey);
		Object o = redisTemplate.execute(seizeCouponScript, keys, activityId, userId, userId + "_" + activityId);
		if (ObjectUtils.isNull(o)) {
			throw new CommonException(SEIZE_COUPON_FAILD, "领取失败");
		}

		// 活动 id
		long result = Long.parseLong(o.toString());
		if (result > 0) {
			// 4.成功，直接return
			return;
		}
		// 5.失败，抛出异常
		if (result == -1) {
			throw new CommonException(ErrorInfo.Code.SEIZE_COUPON_FAILD, "限领一张");
		}
		if (result == -2 || result == -4) {
			throw new CommonException(ErrorInfo.Code.SEIZE_COUPON_FAILD, "已抢光");
		}
		throw new CommonException(ErrorInfo.Code.SEIZE_COUPON_FAILD, "领取失败");
	}

	/**
	 * 新增优惠券领取记录，并更新活动库存
	 *
	 * @param userId     领取人id
	 * @param activityId 活动id
	 * @return:
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void addCouponReceiveRecord(Long userId, Long activityId) {
		// 查询活动信息
		Activity activity = activityService.getById(activityId);
		if (ObjectUtils.isNull(activity)) {
			throw new BadRequestException("活动不存在");
		}
		CommonUserResDTO userInfo = commonUserApi.findById(userId);
		// 新增优惠券领取记录
		Coupon coupon = new Coupon();
		coupon.setId(IdUtils.getSnowflakeNextId());
		coupon.setName(activity.getName());
		coupon.setUserId(userId);
		coupon.setUserName(userInfo.getNickname());
		coupon.setUserPhone(userInfo.getPhone());
		coupon.setActivityId(activityId);
		coupon.setType(activity.getType());
		coupon.setDiscountRate(activity.getDiscountRate());
		coupon.setDiscountAmount(activity.getDiscountAmount());
		coupon.setAmountCondition(activity.getAmountCondition());
		// 当前时间 + 有效期天数
		coupon.setValidityTime(DateUtils.now().plusDays(activity.getValidityDays()));
		coupon.setStatus(NO_USE.getStatus());
		boolean flag = this.save(coupon);
		if (!flag) {
			throw new CommonException("领取失败");
		}

		// 更新活动库存
		flag = activityService.lambdaUpdate().set(Activity::getStockNum, activity.getStockNum() - 1).eq(Activity::getId,
			activityId
		).ge(Activity::getStockNum, 0).update();
		if (!flag) {
			throw new CommonException("领取失败");
		}
	}

	/**
	 * 获取可用的优惠券列表
	 *
	 * @param totalAmount 订单总金额
	 * @return:
	 */
	@Override
	public List<AvailableCouponsResDTO> getAvailableCoupons(BigDecimal totalAmount) {
		if (totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
			return List.of();
		}
		// 1. 查询所有可用优惠券
		List<Coupon> couponList = this.lambdaQuery()
			.eq(Coupon::getUserId, UserContext.currentUserId())
			.eq(Coupon::getStatus, NO_USE.getStatus())
			.gt(Coupon::getValidityTime, DateUtils.now())
			.le(Coupon::getAmountCondition, totalAmount)
			.list();

		if (CollUtils.isEmpty(couponList)) {
			return List.of();
		}

		// 2. 计算优惠券可用金额
		return couponList.stream().peek(coupon -> coupon.setDiscountAmount(CouponUtils.calDiscountAmount(coupon,
				totalAmount
			)))
			// 优惠金额大于0且小于等于订单总金额
			.filter(coupon -> coupon.getDiscountAmount().compareTo(BigDecimal.ZERO) > 0 &&
				coupon.getDiscountAmount().compareTo(totalAmount) <= 0)
			// 类型转换
			.map(coupon -> BeanUtils.copyBean(coupon, AvailableCouponsResDTO.class))
			// 按优惠金额倒序排序
			.sorted(Comparator.comparing(AvailableCouponsResDTO::getDiscountAmount).reversed()).collect(Collectors.toList());
	}

	/**
	 * 使用优惠券
	 *
	 * @param couponUseReqDTO 优惠券使用请求参数
	 * @return:
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public CouponUseResDTO useCoupon(CouponUseReqDTO couponUseReqDTO) {
		// 1. 校验参数
		if (ObjectUtils.isNull(couponUseReqDTO) ||
			ObjectUtils.isNull(couponUseReqDTO.getId()) ||
			ObjectUtils.isNull(couponUseReqDTO.getOrdersId()) ||
			ObjectUtils.isNull(couponUseReqDTO.getTotalAmount()) ||
			couponUseReqDTO.getTotalAmount().compareTo(BigDecimal.ZERO) < 0) {
			throw new CommonException("请求参数错误");
		}

		LocalDateTime now = DateUtils.now();
		Long userId = UserContext.currentUserId();
		if (ObjectUtils.isNull(userId)) {
			throw new CommonException("请先登录");
		}
		Coupon coupon = this.getById(couponUseReqDTO.getId());
		if (ObjectUtils.isNull(coupon)) {
			throw new CommonException("优惠券不存在");
		}
		if (ObjectUtils.equal(coupon.getStatus(), INVALID.getStatus()) || ObjectUtils.equal(coupon.getStatus(),
			USED.getStatus()
		)) {
			throw new CommonException("优惠券已使用或已失效");
		}
		// 2. 更新优惠卷信息
		boolean flag = this.lambdaUpdate()
			.set(Coupon::getUseTime, now)
			.set(Coupon::getStatus, USED.getStatus())
			.set(Coupon::getOrdersId, couponUseReqDTO.getOrdersId())
			.eq(Coupon::getStatus, NO_USE.getStatus())
			.eq(Coupon::getUserId, userId)
			.eq(Coupon::getId, couponUseReqDTO.getId())
			.gt(Coupon::getValidityTime, now)
			.le(Coupon::getAmountCondition, couponUseReqDTO.getTotalAmount())
			.update();
		if (!flag) {
			throw new CommonException("使用优惠券失败");
		}
		// 3. 写入核销表
		CouponWriteOff couponWriteOff = new CouponWriteOff();
		couponWriteOff.setCouponId(couponUseReqDTO.getId());
		couponWriteOff.setUserId(UserContext.currentUserId());
		couponWriteOff.setOrdersId(couponUseReqDTO.getOrdersId());
		couponWriteOff.setActivityId(coupon.getActivityId());
		couponWriteOff.setWriteOffTime(now);
		couponWriteOff.setWriteOffManPhone(coupon.getUserPhone());
		couponWriteOff.setWriteOffManName(coupon.getUserName());

		flag = couponWriteOffService.save(couponWriteOff);
		if (!flag) {
			throw new CommonException("核销失败");
		}
		// 4. 计算优惠券折扣金额
		BigDecimal discountAmount = CouponUtils.calDiscountAmount(coupon, couponUseReqDTO.getTotalAmount());
		// 5. 返回结果
		CouponUseResDTO couponUseResDTO = new CouponUseResDTO();
		couponUseResDTO.setDiscountAmount(discountAmount);
		return couponUseResDTO;
	}

}
